package cn.lingyangwl.agile.infra.file.channel;

import cn.lingyangwl.agile.dynamic.config.core.ConfigFactory;
import cn.lingyangwl.agile.dynamic.config.core.event.ConfigDeleteEvent;
import cn.lingyangwl.agile.model.enums.ConfigDefineEnum;
import cn.lingyangwl.agile.model.enums.ConfigKeyEnum;
import cn.lingyangwl.agile.model.enums.StorageTypeEnum;
import cn.lingyangwl.agile.model.module.config.ConfigInfo;
import cn.lingyangwl.agile.model.module.config.param.BaseConfigParam;
import cn.lingyangwl.agile.model.module.config.param.StorageParam;
import cn.lingyangwl.agile.model.module.tenant.TenantContext;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author shenguangyang
 */
@Slf4j
@Component
public class StorageChannelManager {
    /**
     * 存储渠道类, 方便通过反射动态创建类
     */
    private static final Map<StorageTypeEnum, Class<? extends IStorageChannel>> channelClassCache = new ConcurrentHashMap<>();
    /**
     * 存放渠道对象
     * key: tenantId
     */
    private static final Map<String, IStorageChannel> channelObjectMap = new ConcurrentHashMap<>();
    @Resource
    private ConfigFactory configFactory;


    @PostConstruct
    public void init() {
        // 反射获取所有子类
        Reflections reflection = new Reflections(new ConfigurationBuilder()
                .setScanners(Scanners.SubTypes)
                .setUrls(ClasspathHelper.forPackage(AbstractStorageChannel.class.getPackage().getName()))
                .filterInputsBy(new FilterBuilder()));
        Set<Class<? extends AbstractStorageChannel>> subTypesOf = reflection.getSubTypesOf(AbstractStorageChannel.class);
        for (Class<? extends AbstractStorageChannel> subClass : subTypesOf) {
            try {
                boolean isAbstract = Modifier.isAbstract(subClass.getModifiers());
                if (isAbstract) {
                    continue;
                }

                AbstractStorageChannel channel = subClass.getDeclaredConstructor().newInstance();
                channelClassCache.put(channel.storageType(), subClass);
            } catch (Exception e) {
                log.info("inti class {} fail", subClass.getName());
                throw new BizException(e.getMessage());
            }
        }
    }

    private String getCacheKey() {
        return String.valueOf(TenantContext.get().getTenantId());
    }

    /**
     * 获取渠道客户端
     */
    public IStorageChannel getStorageChannel() {
        String cacheKey = getCacheKey();

        if (!channelObjectMap.containsKey(cacheKey)) {
            createStorageChannel();
        }
        return channelObjectMap.get(cacheKey);
    }

    /**
     *
     */
    private void createStorageChannel() {
        String cacheKey = getCacheKey();
        synchronized (cacheKey.intern()) {
            if (channelObjectMap.containsKey(cacheKey)) {
                return;
            }

            ConfigInfo param = configFactory.getConfigOfNonNull(ConfigDefineEnum.STORAGE);

            // 创建存储渠道
            StorageTypeEnum storageType = StorageTypeEnum.ofByBindConfig(param.getConfigKey());
            Class<? extends IStorageChannel> storageChannelClass = channelClassCache.get(storageType);
            if (Objects.isNull(storageChannelClass)) {
                throw new BizException("存储渠道客户端 {} 尚未实现", storageType);
            }

            try {
                IStorageChannel storageChannel = storageChannelClass.getDeclaredConstructor().newInstance();

                // 获取存储绑定的配置
                ConfigKeyEnum configKeyEnum = storageType.getBindConfig();
                BaseConfigParam<?> configParam = BaseConfigParam.jsonToObject(configKeyEnum, param.getConfigValue());
                storageChannel.createClient((StorageParam<?>) configParam);
                channelObjectMap.put(cacheKey, storageChannel);
            } catch (Exception e) {
                log.error("", e);
                throw new BizException(e.getMessage());
            }
        }
    }

    @EventListener(ConfigDeleteEvent.class)
    public void defaultDelete(ConfigDeleteEvent event) {
        if (!ConfigDefineEnum.STORAGE.name().equals(event.getSource())) {
            return;
        }
        String tenantId = TenantContext.get().getTenantId();
        log.info("delete storage channel client ===> tenantId: {}", tenantId);
        channelObjectMap.remove(getCacheKey());
    }
}
